package com.smartcrop;

import java.awt.image.BufferedImage;
import java.io.File;
import java.util.ArrayList;

import javax.imageio.ImageIO;

public class Smartcrop {

	public static void main(String[] ll) {
		long t = System.currentTimeMillis();

		// CropResult r = crop("17.png", new CropOptions(), 500, 100);
		CropResult r = crop("nl _3_.JPG", new CropOptions(), 250, 250);

		System.out.println("耗时：" + (System.currentTimeMillis() - t));

		System.out.println("x = " + r.topCrop.x);
		System.out.println("y = " + r.topCrop.y);
		System.out.println("w = " + r.topCrop.width);
		System.out.println("h = " + r.topCrop.height);
	}

	public static CropResult crop(String image, CropOptions options, int w, int h) {

		options.width = w;
		options.height = h;

		double scale = 1;
		double prescale = 1;
		File file = new File(image);
		BufferedImage bi = null;
		try {
			bi = ImageIO.read(file);
		} catch (Exception e) {
			e.printStackTrace();
		}
		int width = bi.getWidth();
		int height = bi.getHeight();

		scale = Math.min((double) width / options.width, (double) height / options.height);
		// 按比例在原图上需要截图的长宽
		options.cropWidth = (int) (options.width * scale);// 取整
		options.cropHeight = (int) (options.height * scale);// 取整
		// Img = 100x100, width = 95x95, scale = 100/95, 1/scale > min
		// don't set minscale smaller than 1/scale
		// -> don't pick crops that need upscaling
		// 最小比例，最大比例是1，最小比例不能超过最大比例
		options.minScale = Math.min(options.maxScale, Math.max(1 / scale, options.minScale));

		//
		if (options.prescale != false) {

			// System.out.println("options.prescale = " + options.prescale);

			prescale = 1 / scale / options.minScale;
			if (prescale < 1) {

				// System.out.println("prescale = " + prescale);

				// image = iop.resample(image, width * prescale, height *
				// prescale);
				// 取整
				options.cropWidth = (int) (options.cropWidth * prescale);
				options.cropHeight = (int) (options.cropHeight * prescale);

//				System.out.println("options.cropWidth = " + options.cropWidth);
//				System.out
//						.println("options.cropHeight = " + options.cropHeight);

				// if (options.boost) {
				// options.boost = options.boost.map(function(boost) {
				// return {
				// x: ~~(boost.x * prescale),
				// y: ~~(boost.y * prescale),
				// width: ~~(boost.width * prescale),
				// height: ~~(boost.height * prescale),
				// weight: boost.weight
				// };
				// });
				// }
			} else {
				prescale = 1;
			}
		}

		// Crop data = new Crop(width, height);

		bi = zoomImage(bi, prescale);
		width = bi.getWidth();
		height = bi.getHeight();
		Crop data = new Crop(width, height);

		int[] rgb = new int[4];
		for (int j = 0; j < height; j++) {
			for (int i = 0; i < width; i++) {
				int pixel = bi.getRGB(i, j); // 下面三行代码将一个数字转换为RGB数字
				rgb[0] = (pixel & 0xff0000) >> 16;
				rgb[1] = (pixel & 0xff00) >> 8;
				rgb[2] = (pixel & 0xff);
				rgb[3] = (pixel & 0xff000000) >> 24;

				int p = (j * width + i) * 4;

				data.data[p] = rgb[0];
				data.data[p + 1] = rgb[1];
				data.data[p + 2] = rgb[2];
				data.data[p + 3] = rgb[3];

			}
		}

		CropResult result = analyse(options, data);

		result.topCrop.x = (int) (result.topCrop.x / prescale);
		result.topCrop.y = (int) (result.topCrop.y / prescale);
		result.topCrop.width = (int) (result.topCrop.width / prescale);
		result.topCrop.height = (int) (result.topCrop.height / prescale);

		return result;
	}

	public static BufferedImage zoomImage(BufferedImage im, double resizeTimes) {

		BufferedImage result = null;

		try {
//			File srcfile = new File(src);
//			if (!srcfile.exists()) {
//				System.out.println("文件不存在");
//
//			}
//			BufferedImage im = ImageIO.read(srcfile);

			/* 原始图像的宽度和高度 */
			int width = im.getWidth();
			int height = im.getHeight();

			// 压缩计算
			// float resizeTimes = prescale; /*这个参数是要转化成的倍数,如果是1就是转化成1倍*/

			/* 调整后的图片的宽度和高度 */
			int toWidth = (int) (width * resizeTimes);
			int toHeight = (int) (height * resizeTimes);

			/* 新生成结果图片 */
			result = new BufferedImage(toWidth, toHeight, BufferedImage.TYPE_INT_RGB);

			result.getGraphics().drawImage(im.getScaledInstance(toWidth, toHeight, java.awt.Image.SCALE_SMOOTH), 0, 0,
					null);

		} catch (Exception e) {
			System.out.println("创建缩略图发生异常" + e.getMessage());
		}

		return result;

	}

	// 分析
	public static CropResult analyse(CropOptions options, Crop input) {
		CropResult result = new CropResult();
		Crop output = new Crop(input.width, input.height);

		edgeDetect(input, output);
		skinDetect(options, input, output);
		saturationDetect(options, input, output);
		// applyBoosts(options, output);

		// for (int y = 0; y < input.height; y++) {
		// for (int x = 0; x < input.width; x++) {
		// int i = (y * input.width + x) * 4;
		// System.out.println(i + " :" + output.data[i] + ":"+ output.data[i+1]
		// + ":"+ output.data[i+2] + ":"+ output.data[i+3]);
		// }
		// }

		Crop scoreOutput = downSample(output, options.scoreDownSample);

		double topScore = -Double.MIN_VALUE;
		Crop topCrop = null;
		ArrayList<Crop> crops = generateCrops(options, input.width, input.height);

		System.out.println("crops = " + crops.size());

		for (int i = 0, iLen = crops.size(); i < iLen; i++) {
			Crop crop = crops.get(i);
			crop.score = score(options, scoreOutput, crop);
			// System.out.println("x = " + crop.x);
			// System.out.println("score = " + crop.score.total);
			if (i == 0) {
				topCrop = crop;
				topScore = crop.score.total;
			}
			if (crop.score.total > topScore) {
				topCrop = crop;
				topScore = crop.score.total;
			}

		}

		result.topCrop = topCrop;
		return result;
	}

	// 边沿检测
	public static void edgeDetect(Crop i, Crop o) {
		double[] id = i.data;// 像素信息
		double[] od = o.data;// 输出信息
		int w = i.width;
		int h = i.height;

		for (int y = 0; y < h; y++) {
			for (int x = 0; x < w; x++) {
				int p = (y * w + x) * 4;
				double lightness;

				if (x == 0 || x >= w - 1 || y == 0 || y >= h - 1) {
					lightness = sample(id, p);
				} else {
					// 取当前值 减去 上下左右的值
					lightness = sample(id, p) * 4 - sample(id, p - w * 4) - sample(id, p - 4) - sample(id, p + 4)
							- sample(id, p + w * 4);
				}

				od[p + 1] = lightness > 0 ? lightness : 0;
				// System.out.println("od[p + 1] = " + p + "："+ od[p + 1]);
			}
		}
	}

	// 皮肤检测
	public static void skinDetect(CropOptions options, Crop i, Crop o) {
		double[] id = i.data;
		double[] od = o.data;
		int w = i.width;
		int h = i.height;

		for (int y = 0; y < h; y++) {
			for (int x = 0; x < w; x++) {
				int p = (y * w + x) * 4;
				double lightness = cie(id[p], id[p + 1], id[p + 2]) / 255;
				double skin = skinColor(options, id[p], id[p + 1], id[p + 2]);
				boolean isSkinColor = skin > options.skinThreshold;
				boolean isSkinBrightness = lightness >= options.skinBrightnessMin
						&& lightness <= options.skinBrightnessMax;
				if (isSkinColor && isSkinBrightness) {
					od[p] = (skin - options.skinThreshold) * (255 / (1 - options.skinThreshold));
				} else {
					od[p] = 0;
				}

				// System.out.println("od[p] = " + p + ":"+ od[p]);
			}
		}
	}

	// 饱和度检测
	public static void saturationDetect(CropOptions options, Crop i, Crop o) {
		double[] id = i.data;
		double[] od = o.data;
		int w = i.width;
		int h = i.height;
		for (int y = 0; y < h; y++) {
			for (int x = 0; x < w; x++) {
				int p = (y * w + x) * 4;

				double lightness = cie(id[p], id[p + 1], id[p + 2]) / 255;
				double sat = saturation(id[p], id[p + 1], id[p + 2]);

				// System.out.println("od[p + 2] = " + p + ":"+ sat);

				boolean acceptableSaturation = sat > options.saturationThreshold;
				boolean acceptableLightness = lightness >= options.saturationBrightnessMin
						&& lightness <= options.saturationBrightnessMax;
				if (acceptableSaturation && acceptableLightness) {
					od[p + 2] = (sat - options.saturationThreshold) * (255 / (1 - options.saturationThreshold));
					// System.out.println("od[p + 2] = true " + p + ":" + sat +
					// ":"+ od[p + 2]);
				} else {
					od[p + 2] = 0;
					// System.out.println("od[p + 2] = false " + p + ":" + sat +
					// ":"+ od[p + 2]);
				}

				// System.out.println("od[p + 2] = " + p + ":"+ od[p + 2]);
			}
		}
	}

	//
	public static Crop downSample(Crop input, double factor) {

		// System.out.println("factor = " + factor);

		double[] idata = input.data;
		int iwidth = input.width;
		int width = (int) Math.floor(input.width / factor);
		int height = (int) Math.floor(input.height / factor);
		Crop output = new Crop(width, height);
		double[] data = output.data;
		double ifactor2 = 1d / (factor * factor);

		// System.out.println("width = " + width);
		// System.out.println("height = " + height);
		// System.out.println("ifactor2 = " + ifactor2);

		for (int y = 0; y < height; y++) {
			for (int x = 0; x < width; x++) {
				int i = (y * width + x) * 4;

				double r = 0;
				double g = 0;
				double b = 0;
				double a = 0;

				double mr = 0;
				double mg = 0;
				double mb = 0;

				for (int v = 0; v < factor; v++) {
					for (int u = 0; u < factor; u++) {
						int j = (int) (((y * factor + v) * iwidth + (x * factor + u)) * 4);
						r += idata[j];
						g += idata[j + 1];
						b += idata[j + 2];
						a += idata[j + 3];
						mr = Math.max(mr, idata[j]);
						mg = Math.max(mg, idata[j + 1]);
						mb = Math.max(mb, idata[j + 2]);

						// System.out.println(j + ": " + idata[j] + ":"+
						// idata[j+ 1] + ":" + idata[j+ 2] + ":"+ idata[j+ 3]);

					}
				}
				// this is some funky magic to preserve detail a bit more for
				// skin (r) and detail (g). Saturation (b) does not get this
				// boost.
				data[i] = r * ifactor2 * 0.5 + mr * 0.5;
				data[i + 1] = g * ifactor2 * 0.7 + mg * 0.3;
				data[i + 2] = b * ifactor2;
				data[i + 3] = a * ifactor2;

				// System.out.println(i + ": " + data[i] + ":"+ data[i+ 1] + ":"
				// + data[i+ 2] + ":"+ data[i+ 3]);
			}
		}
		return output;
	}

	public static ArrayList<Crop> generateCrops(CropOptions options, int width, int height) {
		ArrayList<Crop> results = new ArrayList<Crop>();
		int minDimension = Math.min(width, height);
		int cropWidth = options.cropWidth > 0 ? options.cropWidth : minDimension;
		int cropHeight = options.cropHeight > 0 ? options.cropHeight : minDimension;

		// System.out.println("cropWidth = " + cropWidth);
		// System.out.println("cropHeight = " + cropHeight);

		for (int scale = options.maxScale; scale >= options.minScale; scale -= options.scaleStep) {
			for (int y = 0; y + cropHeight * scale <= height; y += options.step) {
				for (int x = 0; x + cropWidth * scale <= width; x += options.step) {
					Crop crop = new Crop();
					crop.x = x;
					crop.y = y;
					crop.width = cropWidth;
					crop.height = cropHeight;
					results.add(crop);
				}
			}
		}
		return results;
	}

	// 得分
	public static Score score(CropOptions options, Crop output, Crop crop) {
		// var result = {
		// detail: 0,
		// saturation: 0,
		// skin: 0,
		// boost: 0,
		// total: 0,
		// };

		Score result = new Score();

		double[] od = output.data;
		double downSample = options.scoreDownSample;
		double invDownSample = 1 / downSample;
		int outputHeightDownSample = (int) (output.height * downSample);
		int outputWidthDownSample = (int) (output.width * downSample);
		int outputWidth = output.width;

		for (int y = 0; y < outputHeightDownSample; y += downSample) {
			for (int x = 0; x < outputWidthDownSample; x += downSample) {
				int p = (int) (((y * invDownSample) * outputWidth + (x * invDownSample)) * 4);
				double i = importance(options, crop, x, y);
				double detail = od[p + 1] / 255;

				result.skin += od[p] / 255 * (detail + options.skinBias) * i;
				result.detail += detail * i;
				result.saturation += od[p + 2] / 255 * (detail + options.saturationBias) * i;
				result.boost += od[p + 3] / 255 * i;

				// System.out.println("p :"+ p);
				// System.out.println("i :"+ i);
				// System.out.println(result.skin + ":"+ result.detail+ ":"+
				// result.saturation+ ":"+ result.boost);
			}
		}

		// System.out.println(result.skin + ":"+ result.detail+ ":"+
		// result.saturation+ ":"+ result.boost);

		// System.out.println("options.edgeRadius :"+ options.edgeRadius);
		// System.out.println("options.edgeWeight :"+ options.edgeWeight);

		result.total = (result.detail * options.detailWeight + result.skin * options.skinWeight
				+ result.saturation * options.saturationWeight + result.boost * options.boostWeight)
				/ (crop.width * crop.height);
		return result;
	}

	public static double importance(CropOptions options, Crop crop, double x, double y) {
		if (crop.x > x || x >= crop.x + crop.width || crop.y > y || y >= crop.y + crop.height) {
			return options.outsideImportance;
		}
		x = (x - crop.x) / crop.width;
		y = (y - crop.y) / crop.height;
		double px = Math.abs(0.5 - x) * 2;
		double py = Math.abs(0.5 - y) * 2;
		// Distance from edge
		double dx = Math.max(px - 1.0 + options.edgeRadius, 0);
		double dy = Math.max(py - 1.0 + options.edgeRadius, 0);
		double d = (dx * dx + dy * dy) * options.edgeWeight;
		double s = 1.41 - Math.sqrt(px * px + py * py);

		// System.out.println(d + " :"+ s);
		// System.out.println(px + " :"+ py);

		return s + d;
	}

	// 皮肤颜色
	public static double skinColor(CropOptions options, double r, double g, double b) {
		double mag = Math.sqrt(r * r + g * g + b * b);
		double rd = (r / mag - options.skinColor[0]);
		double gd = (g / mag - options.skinColor[1]);
		double bd = (b / mag - options.skinColor[2]);
		double d = Math.sqrt(rd * rd + gd * gd + bd * bd);
		return 1 - d;
	}

	// 光度
	public static double cie(double r, double g, double b) {
		return 0.5126 * b + 0.7152 * g + 0.0722 * r;
	}

	// 取样，取当前像素的rgb
	public static double sample(double[] id, int p) {
		return cie(id[p], id[p + 1], id[p + 2]);
	}

	// 饱和度
	public static double saturation(double r, double g, double b) {
		double maximum = Math.max(r / 255, g / 255);
		maximum = Math.max(maximum, b / 255);
		double minumum = Math.min(r / 255, g / 255);
		minumum = Math.min(minumum, b / 255);

		if (maximum == minumum) {
			return 0;
		}

		double l = (maximum + minumum) / 2;
		double d = maximum - minumum;

		return l > 0.5 ? d / (2 - maximum - minumum) : d / (maximum + minumum);
	}

}
